package rkw.database;

import java.util.List;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import rkw.database.model.AdministratorToken;
import rkw.database.model.Election;
import rkw.database.model.ElectionVoter;
import rkw.database.model.Member;
import rkw.database.model.MessageReceived;
import rkw.database.model.MessageToSend;
import rkw.database.model.Node;
import rkw.database.model.Token;
import rkw.database.model.UnsendVoterId;
import rkw.database.model.Vote;
import rkw.database.model.Voter;
import rkw.database.repositories.AdministratorTokenRepository;
import rkw.database.repositories.ElectionRepository;
import rkw.database.repositories.ElectionVoterRepository;
import rkw.database.repositories.MemberRepository;
import rkw.database.repositories.MessageReceivedRepository;
import rkw.database.repositories.MessageToSendRepository;
import rkw.database.repositories.NodeRepository;
import rkw.database.repositories.TokenRepository;
import rkw.database.repositories.UnsendVoterIdRepository;
import rkw.database.repositories.VoteRepository;
import rkw.database.repositories.VoterRepository;
import core.communication.message.Direction;
import core.communication.message.Message;
import core.database.ElectionStatus;
import core.utils.ConfigPropertiesHolder;
import core.utils.Converter;
import core.utils.Property;

@Service
@Transactional
public class DatabaseServiceImpl implements DatabaseService {

	private static final Logger L = LoggerFactory.getLogger(DatabaseServiceImpl.class);

	@Autowired
	private AdministratorTokenRepository administratorTokenRepository;
	@Autowired
	private ElectionRepository electionRepository;
	@Autowired
	private ElectionVoterRepository electionVoterRepository;
	@Autowired
	private MemberRepository memberRepository;
	@Autowired
	private MessageToSendRepository messageToSendRepository;
	@Autowired
	private MessageReceivedRepository messageReceivedRepository;
	@Autowired
	private NodeRepository nodeRepository;
	@Autowired
	private VoterRepository voterRepository;
	@Autowired
	private TokenRepository tokenRepository;
	@Autowired
	private VoteRepository voteRepository;
	@Autowired
	private UnsendVoterIdRepository unsendVoterIdRepository;

	public DatabaseServiceImpl() {
		L.debug("Tworzę DatabaseService");
		L.debug("Utworzono DatabaseService");
	}

	@Override
	public Node getMainNode() {
		return nodeRepository.findByLevel(0);
	}

	@Override
	public Node storeNode(Node node) {
		node = nodeRepository.saveAndFlush(node);

		L.debug("Dodałem nowy węzeł: {}", node);

		return node;
	}

	@Override
	public void deleteNode(Node node) {
		L.debug("Usuwam obiekt: {}", node);
		nodeRepository.delete(node);
	}

	@Override
	public Boolean isNodeLocatorUriTaken(String ip, String port) {
		return nodeRepository.findByIpAndPort(ip, port) != null;
	}

	@Override
	public List<MessageToSend> getMessagesToSend() {
		L.debug("Pobieram messagesToSend");

		return messageToSendRepository.findAll();
	}

	@Override
	public List<MessageReceived> getMessagesReceived() {
		L.debug("Pobieram messagesReceived");

		return messageReceivedRepository.findAllByIsHandledFalseOrderByCreatedAsc();
	}

	@Override
	public List<Node> getNodesForDirection(Direction direction) {
		if (direction == Direction.UP)
			return nodeRepository.findAllByLevel(1);
		else
			return nodeRepository.findAllByLevel(-1);
	}

	@Override
	public void storeMessageToSend(Message message) {
		L.debug("saveMessageToSend {}", message);

		MessageToSend messageTS = new MessageToSend();
		messageTS.setValue(Converter.objectToXML(message));
		messageTS.setType(message.getMessageType());
		messageTS = messageToSendRepository.save(messageTS);
		message.setMessageToSendDatabaseId(messageTS.getId());
	}

	@Override
	public void saveReceivedMessage(Message message) {
		L.debug("saveReceivedMessage {}", message);

		MessageReceived messageR = new MessageReceived();
		messageR.setValue(Converter.objectToXML(message));
		messageR.setType(message.getMessageType());
		messageR = messageReceivedRepository.save(messageR);
		message.setMessageToSendDatabaseId(messageR.getId());
	}

	@Override
	public void setMessageAsHandled(Long messageToSendDatabaseId) {
		MessageReceived message = messageReceivedRepository.findOne(messageToSendDatabaseId);
		message.setIsHandled(true);
		L.debug("Zapisuje obsłużenie wiadomości {}", message);
		messageReceivedRepository.save(message);
	}

	@Override
	public void removeMessageToSend(Long messageToSendDatabaseId) {
		L.debug("Usuwam messagesToSend o id: {}", messageToSendDatabaseId);

		messageToSendRepository.delete(messageToSendDatabaseId);
	}

	@Override
	public Node updateNode(String targetNodeUri, Node newNode) {
		Node node = nodeRepository.findByIpAndPort(Node.getIp(targetNodeUri), Node.getPort(targetNodeUri));
		node.setName(newNode.getName());
		node.setId(newNode.getId());
		node.setPort(newNode.getPort());
		node.setAddress(newNode.getAddress());
		node.setLevel(newNode.getLevel());
		node.setNodeType(newNode.getNodeType());

		return nodeRepository.saveAndFlush(node);
	}

	@Override
	public void deleteNode(String locatorUri) {
		Node node = nodeRepository.findByIpAndPort(Node.getIp(locatorUri), Node.getPort(locatorUri));
		nodeRepository.delete(node);
	}

	@Override
	public Election getElection(Long electionIdentificationField) {
		return electionRepository.findByIdentificationField(electionIdentificationField);
	}

	@Override
	public void deleteElection(Election election) {
		electionRepository.delete(election);
	}

	@Override
	public Election storeElection(Election election) {
		return electionRepository.saveAndFlush(election);
	}

	@Override
	public Member getMemberByIdentificationField(String identificationField) {
		return memberRepository.findByIdentificationField(identificationField);
	}

	@Override
	public Voter getVoterByIdentificationField(String identificationField) {
		return voterRepository.findByIdentificationField(identificationField);
	}

	@Override
	public List<Election> getAllElections() {
		return electionRepository.findAll();
	}

	@Override
	public Voter getVoter(Long id) {
		return voterRepository.findOne(id);
	}

	@Override
	public void deleteVoter(Voter voter) {
		voterRepository.delete(voter);
	}

	@Override
	public Member getMember(Long id) {
		return memberRepository.findOne(id);
	}

	@Override
	public void deleteMember(Member member) {
		memberRepository.delete(member);
	}

	@Override
	public Voter storeVoter(Voter voter) {
		return voterRepository.saveAndFlush(voter);
	}

	@Override
	public Member storeMember(Member member) {
		return memberRepository.saveAndFlush(member);
	}

	@Override
	public AdministratorToken getAdministratorTokenByMemberAndElection(Member member, Election election) {
		return administratorTokenRepository.findByMemberAndElection(member, election);
	}

	@Override
	public AdministratorToken getAdministratorTokenByValue(String value) {
		return administratorTokenRepository.findByValue(value);
	}

	@Override
	public Boolean verifyVoter(Long electionIdentificationField, String voterIdentificationField) {
		ElectionVoter voter = electionVoterRepository.findByElectionIdAndVoterId(electionIdentificationField, voterIdentificationField);
		if (voter == null)
			return false;

		if (voter.getHasVoted() == true)
			return false;
		else {
			voter.setHasVoted(true);
			voter = electionVoterRepository.saveAndFlush(voter);
			return true;
		}
	}

	@Override
	public List<ElectionVoter> findVotersLike(Election election, String text) {
		text = text.toLowerCase();
		text = "%" + text + "%";

		return electionVoterRepository.findAllLike(election, text);
	}

	@Override
	public boolean handleVote(UUID token, Vote vote) {
		if (ConfigPropertiesHolder.getInstance().getProperty(Property.DEBUG_STATE).equals("false")) {
			Token t = tokenRepository.findTokenByValue(token.toString());
			if (t != null) {
				tokenRepository.delete(t);
				voteRepository.saveAndFlush(vote);
				return true;
			}
			return false;
		} else {
			voteRepository.saveAndFlush(vote);
			return false;
		}
	}

	@Override
	public void saveToken(Token token) {
		tokenRepository.saveAndFlush(token);
	}

	@Override
	public Election getStartedElection() {
		return electionRepository.findByStatus(ElectionStatus.INPROGRESS);
	}

	@Override
	public AdministratorToken saveAdministratorToken(AdministratorToken administratorToken) {
		return administratorTokenRepository.saveAndFlush(administratorToken);
	}

	@Override
	public List<Vote> getUnsendVotes() {
		return voteRepository.findAllBySend(false);
	}

	@Override
	public void updateVotes(List<Vote> votes) {
		voteRepository.save(votes);
	}

	@Override
	public UnsendVoterId storeUnsendVoterId(UnsendVoterId unsendVoterId) {
		return unsendVoterIdRepository.saveAndFlush(unsendVoterId);
	}

	@Override
	public List<UnsendVoterId> getUnsendVoterIds() {
		return unsendVoterIdRepository.findAll();
	}

	@Override
	public void deleteUnsendVoterIds(List<UnsendVoterId> voters) {
		unsendVoterIdRepository.delete(voters);
	}

	@Override
	public Token getTokenByValue(String value) {
		return tokenRepository.findTokenByValue(value);
	}
}
